// ------------------------------------------------------------------------
// 版权信息
// 版权归重庆虫儿飞科技有限公司所有。
// 所有权利保留。
// 官方网站：https://netokit.com
// 许可证信息
// Neto.Kit 项目主要遵循 MIT 许可证和 Apache 许可证（版本 2.0）进行分发和使用。
// 许可证的完整文本可以在源代码树根目录中的 LICENSE-APACHE 和 LICENSE-MIT 文件中找到。
// 
// 使用条款
// 使用本代码应遵守相关法律法规和许可证的要求。
// 
// 免责声明
// 对于因使用本代码而产生的任何直接、间接、偶然、特殊或后果性损害，我们不承担任何责任。
// 
// 其他重要信息
// Neto.Kit 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。
// 有关 Neto.Kit 项目的其他详细信息，请参阅位于源代码树根目录中的 COPYRIGHT 和 DISCLAIMER 文件。
// 
// 更多信息
// 请访问 https://netokit.com 获取更多关于 Neto.Kit 项目的许可证和版权信息。
// ------------------------------------------------------------------------

#region

using System.Security.Cryptography;
using System.Text;

#endregion

namespace Neto.Application.ApiAdmin.Account;

/// <summary>
///     RSA 加密解密
/// </summary>
public class LoginRsa
{
    private readonly RSACryptoServiceProvider _privateKeyRsaProvider;
    private readonly RSACryptoServiceProvider _publicKeyRsaProvider;

    public LoginRsa(string privateKey, string publicKey = null)
    {
        if (!string.IsNullOrEmpty(privateKey)) _privateKeyRsaProvider = CreateRsaProviderFromPrivateKey(privateKey);

        if (!string.IsNullOrEmpty(publicKey)) _publicKeyRsaProvider = CreateRsaProviderFromPublicKey(publicKey);
    }

    /// <summary>
    ///     RSA解密
    /// </summary>
    /// <param name="cipherText"></param>
    /// <returns></returns>
    public string Decrypt(string cipherText)
    {
        if (_privateKeyRsaProvider == null) throw new Exception("_privateKeyRsaProvider is null");
        return Decrypt2(cipherText);
    }

    /// <summary>
    ///     RSA加密
    /// </summary>
    /// <param name="text"></param>
    /// <returns></returns>
    public string Encrypt(string text)
    {
        if (_publicKeyRsaProvider == null) throw new Exception("_publicKeyRsaProvider is null");
        return Encrypt2(text);
        //return Convert.ToBase64String(_publicKeyRsaProvider.Encrypt(Encoding.UTF8.GetBytes(text), false));
    }

    private string Encrypt2(string text)
    {
        var PlaintextData = Encoding.UTF8.GetBytes(text);
        var MaxBlockSize = (_publicKeyRsaProvider.KeySize / 8) - 11; //加密块最大长度限制

        if (PlaintextData.Length <= MaxBlockSize)
            return Convert.ToBase64String(_publicKeyRsaProvider.Encrypt(PlaintextData, false));
        using (var PlaiStream = new MemoryStream(PlaintextData))
        using (var CrypStream = new MemoryStream())
        {
            var Buffer = new byte[MaxBlockSize];
            var BlockSize = PlaiStream.Read(Buffer, 0, MaxBlockSize);

            while (BlockSize > 0)
            {
                var ToEncrypt = new byte[BlockSize];
                Array.Copy(Buffer, 0, ToEncrypt, 0, BlockSize);

                var Cryptograph = _publicKeyRsaProvider.Encrypt(ToEncrypt, false);
                CrypStream.Write(Cryptograph, 0, Cryptograph.Length);

                BlockSize = PlaiStream.Read(Buffer, 0, MaxBlockSize);
            }

            return Convert.ToBase64String(CrypStream.ToArray(), Base64FormattingOptions.None);
        }
    }

    private string Decrypt2(string ciphertext)
    {
        var CiphertextData = Convert.FromBase64String(ciphertext);
        var MaxBlockSize = _privateKeyRsaProvider.KeySize / 8; //解密块最大长度限制

        if (CiphertextData.Length <= MaxBlockSize)
            return Encoding.UTF8.GetString(_privateKeyRsaProvider.Decrypt(CiphertextData, false));

        using (var CrypStream = new MemoryStream(CiphertextData))
        using (var PlaiStream = new MemoryStream())
        {
            var Buffer = new byte[MaxBlockSize];
            var BlockSize = CrypStream.Read(Buffer, 0, MaxBlockSize);

            while (BlockSize > 0)
            {
                var ToDecrypt = new byte[BlockSize];
                Array.Copy(Buffer, 0, ToDecrypt, 0, BlockSize);

                var Plaintext = _privateKeyRsaProvider.Decrypt(ToDecrypt, false);
                PlaiStream.Write(Plaintext, 0, Plaintext.Length);

                BlockSize = CrypStream.Read(Buffer, 0, MaxBlockSize);
            }

            return Encoding.UTF8.GetString(PlaiStream.ToArray());
        }
    }

    private RSACryptoServiceProvider CreateRsaProviderFromPrivateKey(string privateKey)
    {
        var privateKeyBits = Convert.FromBase64String(privateKey);

        var RSA = new RSACryptoServiceProvider();
        var RSAparams = new RSAParameters();

        using (var binr = new BinaryReader(new MemoryStream(privateKeyBits)))
        {
            byte bt = 0;
            ushort twobytes = 0;
            twobytes = binr.ReadUInt16();
            if (twobytes == 0x8130)
                binr.ReadByte();
            else if (twobytes == 0x8230)
                binr.ReadInt16();
            else
                throw new Exception("Unexpected value read binr.ReadUInt16()");

            twobytes = binr.ReadUInt16();
            if (twobytes != 0x0102)
                throw new Exception("Unexpected version");

            bt = binr.ReadByte();
            if (bt != 0x00)
                throw new Exception("Unexpected value read binr.ReadByte()");

            RSAparams.Modulus = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.Exponent = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.D = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.P = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.Q = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.DP = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.DQ = binr.ReadBytes(GetIntegerSize(binr));
            RSAparams.InverseQ = binr.ReadBytes(GetIntegerSize(binr));
        }

        RSA.ImportParameters(RSAparams);
        return RSA;
    }

    private int GetIntegerSize(BinaryReader binr)
    {
        byte bt = 0;
        byte lowbyte = 0x00;
        byte highbyte = 0x00;
        var count = 0;
        bt = binr.ReadByte();
        if (bt != 0x02)
            return 0;
        bt = binr.ReadByte();

        if (bt == 0x81)
            count = binr.ReadByte();
        else if (bt == 0x82)
        {
            highbyte = binr.ReadByte();
            lowbyte = binr.ReadByte();
            byte[] modint = { lowbyte, highbyte, 0x00, 0x00 };
            count = BitConverter.ToInt32(modint, 0);
        }
        else
            count = bt;

        while (binr.ReadByte() == 0x00) count -= 1;
        binr.BaseStream.Seek(-1, SeekOrigin.Current);
        return count;
    }

    private RSACryptoServiceProvider CreateRsaProviderFromPublicKey(string publicKeyString)
    {
        // encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"
        byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 };
        byte[] x509key;
        var seq = new byte[15];
        int x509size;

        x509key = Convert.FromBase64String(publicKeyString);
        x509size = x509key.Length;

        // --------- Set up stream to read the asn.1 encoded SubjectPublicKeyInfo blob ------
        using (var mem = new MemoryStream(x509key))
        using (var binr = new BinaryReader(mem)) //wrap Memory Stream with BinaryReader for easy reading
        {
            byte bt = 0;
            ushort twobytes = 0;

            twobytes = binr.ReadUInt16();
            if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                binr.ReadByte(); //advance 1 byte
            else if (twobytes == 0x8230)
                binr.ReadInt16(); //advance 2 bytes
            else
                return null;

            seq = binr.ReadBytes(15); //read the Sequence OID
            if (!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correct
                return null;

            twobytes = binr.ReadUInt16();
            if (twobytes == 0x8103) //data read as little endian order (actual data order for Bit String is 03 81)
                binr.ReadByte(); //advance 1 byte
            else if (twobytes == 0x8203)
                binr.ReadInt16(); //advance 2 bytes
            else
                return null;

            bt = binr.ReadByte();
            if (bt != 0x00) //expect null byte next
                return null;

            twobytes = binr.ReadUInt16();
            if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81)
                binr.ReadByte(); //advance 1 byte
            else if (twobytes == 0x8230)
                binr.ReadInt16(); //advance 2 bytes
            else
                return null;

            twobytes = binr.ReadUInt16();
            byte lowbyte = 0x00;
            byte highbyte = 0x00;

            if (twobytes == 0x8102) //data read as little endian order (actual data order for Integer is 02 81)
                lowbyte = binr.ReadByte(); // read next bytes which is bytes in modulus
            else if (twobytes == 0x8202)
            {
                highbyte = binr.ReadByte(); //advance 2 bytes
                lowbyte = binr.ReadByte();
            }
            else
                return null;

            byte[]
                modint = { lowbyte, highbyte, 0x00, 0x00 }; //reverse byte order since asn.1 key uses big endian order
            var modsize = BitConverter.ToInt32(modint, 0);

            var firstbyte = binr.PeekChar();
            if (firstbyte == 0x00)
            {
                //if first byte (highest order) of modulus is zero, don't include it
                binr.ReadByte(); //skip this null byte
                modsize -= 1; //reduce modulus buffer size by 1
            }

            var modulus = binr.ReadBytes(modsize); //read the modulus bytes

            if (binr.ReadByte() != 0x02) //expect an Integer for the exponent data
                return null;
            int expbytes =
                binr.ReadByte(); // should only need one byte for actual exponent data (for all useful values)
            var exponent = binr.ReadBytes(expbytes);

            // ------- create RSACryptoServiceProvider instance and initialize with public key -----
            var RSA = new RSACryptoServiceProvider();
            var RSAKeyInfo = new RSAParameters();
            RSAKeyInfo.Modulus = modulus;
            RSAKeyInfo.Exponent = exponent;
            RSA.ImportParameters(RSAKeyInfo);

            return RSA;
        }
    }

    private bool CompareBytearrays(byte[] a, byte[] b)
    {
        if (a.Length != b.Length)
            return false;
        var i = 0;
        foreach (var c in a)
        {
            if (c != b[i])
                return false;
            i++;
        }

        return true;
    }
}